<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    
    <title>15. Aritmética de Punto Flotante: Problemas y Limitaciones &mdash; Tutorial de Python v2.6.2 documentation</title>
    <link rel="stylesheet" href="_static/default.css" type="text/css" />
    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
    <script type="text/javascript">
      var DOCUMENTATION_OPTIONS = {
        URL_ROOT:    '#',
        VERSION:     '2.6.2',
        COLLAPSE_MODINDEX: false,
        FILE_SUFFIX: '.html',
        HAS_SOURCE:  true
      };
    </script>
    <script type="text/javascript" src="_static/jquery.js"></script>
    <script type="text/javascript" src="_static/doctools.js"></script>
    <link rel="top" title="Tutorial de Python v2.6.2 documentation" href="index.html" />
    <link rel="prev" title="14. Edición de entrada interactiva y sustitución de historial" href="interactive.html" /> 
  </head>
  <body>
    <div class="related">
      <h3>Navegación</h3>
      <ul>
        <li class="right" style="margin-right: 10px">
          <a href="genindex.html" title="Índice General"
             accesskey="I">índice</a></li>
        <li class="right" >
          <a href="interactive.html" title="14. Edición de entrada interactiva y sustitución de historial"
             accesskey="P">anterior</a> |</li>
        <li><a href="contenido.html">Tutorial de Python v2.6.2 documentation</a> &raquo;</li> 
      </ul>
    </div>  

    <div class="document">
      <div class="documentwrapper">
        <div class="bodywrapper">
          <div class="body">
            
  <div class="section" id="aritmetica-de-punto-flotante-problemas-y-limitaciones">
<span id="tut-fp-issues"></span><h1>15. Aritmética de Punto Flotante: Problemas y Limitaciones<a class="headerlink" href="#aritmetica-de-punto-flotante-problemas-y-limitaciones" title="Enlazar permanentemente con este título">¶</a></h1>
<p>Los números de punto flotante se representan en el hardware de la
computadora en fracciones en base 2 (binario).  Por ejemplo, la fracción
decimal</p>
<div class="highlight-python"><div class="highlight"><pre><span class="mf">0.125</span>
</pre></div>
</div>
<p>...tiene el valor 1/10 + 2/100 + 5/1000, y de la misma manera la fracción
binaria</p>
<div class="highlight-python"><div class="highlight"><pre><span class="mf">0.001</span>
</pre></div>
</div>
<p>...tiene el valor 0/2 + 0/4 + 1/8.  Estas dos fracciones tienen valores
idénticos, la única diferencia real es que la primera está escrita en
notación fraccional en base 10 y la segunda en base 2.</p>
<p>Desafortunadamente, la mayoría de las fracciones decimales no pueden
representarse exactamente como fracciones binarias.  Como consecuencia, en
general los números de punto flotante decimal que ingresás en la computadora
son sólo aproximados por los números de punto flotante binario que realmente
se guardan en la máquina.</p>
<p>El problema es más fácil de entender primero en base 10.  Considerá la
fracción 1/3.  Podés aproximarla como una fracción de base 10</p>
<div class="highlight-python"><div class="highlight"><pre><span class="mf">0.3</span>
</pre></div>
</div>
<p>...o, mejor,</p>
<div class="highlight-python"><div class="highlight"><pre><span class="mf">0.33</span>
</pre></div>
</div>
<p>...o, mejor,</p>
<div class="highlight-python"><div class="highlight"><pre><span class="mf">0.333</span>
</pre></div>
</div>
<p>...y así.  No importa cuantos dígitos desees escribir, el resultado nunca será
exactamente 1/3, pero será una aproximación cada vez mejor de 1/3.</p>
<p>De la misma manera, no importa cuantos dígitos en base 2 quieras usar, el
valor decimal 0.1 no puede representarse exactamente como una fracción en
base 2.  En base 2, 1/10 es la siguiente fracción que se repite
infinitamente:</p>
<div class="highlight-python"><pre>0.0001100110011001100110011001100110011001100110011...</pre>
</div>
<p>Frená en cualquier número finito de bits, y tendrás una aproximación.  Es
por esto que ves cosas como:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="mf">0.1</span>
<span class="go">0.10000000000000001</span>
</pre></div>
</div>
<p>En la mayoría de las máquinas de hoy en día, eso es lo que verás si ingresás
0.1 en un prompt de Python.  Quizás no, sin embargo, porque la cantidad de
bits usados por el hardware para almacenar valores de punto flotante puede
variar en las distintas máquinas, y Python sólo muestra una aproximación del
valor decimal verdadero de la aproximación binaria guardada por la máquina.
En la mayoría de las máquinas, si Python fuera a mostrar el verdadero valor
decimal de la aproximación almacenada por 0.1, tendría que mostrar sin
embargo</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="mf">0.1</span>
<span class="go">0.1000000000000000055511151231257827021181583404541015625</span>
</pre></div>
</div>
<p>El prompt de Python usa la función integrada <tt class="xref docutils literal"><span class="pre">repr()</span></tt> para obtener una
versión en cadena de caracteres de todo lo que muestra.  Para flotantes,
<tt class="docutils literal"><span class="pre">repr(float)</span></tt> redondea el valor decimal verdadero a 17 dígitos
significativos, dando</p>
<div class="highlight-python"><div class="highlight"><pre><span class="mf">0.10000000000000001</span>
</pre></div>
</div>
<p><tt class="docutils literal"><span class="pre">repr(float)</span></tt> produce 17 dígitos significativos porque esto es suficiente
(en la mayoría de las máquinas) para que se cumpla <tt class="docutils literal"><span class="pre">eval(repr(x))</span> <span class="pre">==</span> <span class="pre">x</span></tt>
exactamente para todos los flotantes finitos <em>X</em>, pero redondeando a 16
dígitos no es suficiente para que sea verdadero.</p>
<p>Notá que esta es la verdadera naturaleza del punto flotante binario: no es
un error de Python, y tampoco es un error en tu código.  Verás lo mismo en todos
los lenguajes que soportan la aritmética de punto flotante de tu hardware (a
pesar de que en algunos lenguajes por omisión no <em>muestren</em> la diferencia, o
no lo hagan en todos los modos de salida).</p>
<p>La función integrada :func: <cite>str</cite> de Python produce sólo 12 dígitos
significativos, y quizás quieras usar esa.  Normalmente <tt class="docutils literal"><span class="pre">eval(str(x))</span></tt> no
reproducirá <cite>x</cite>, pero la salida quizás sea más placentera de ver:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="nb">str</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="go">0.1</span>
</pre></div>
</div>
<p>Es importante darse cuenta de que esto es, realmente, una ilusión: el valor
en la máquina no es exactamente 1/10, simplemente estás redondeando el valor
que se <em>muestra</em> del valor verdadero de la máquina.</p>
<p>A esta se siguen otras sorpresas.  Por ejemplo, luego de ver:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="mf">0.1</span>
<span class="go">0.10000000000000001</span>
</pre></div>
</div>
<p>...quizás estés tentado de usar la función <tt class="xref docutils literal"><span class="pre">round()</span></tt> para recortar el
resultado al dígito que esperabas.  Pero es lo mismo:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="nb">round</span><span class="p">(</span><span class="mf">0.1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="go">0.10000000000000001</span>
</pre></div>
</div>
<p>El problema es que el valor de punto flotante binario almacenado para &#8220;0.1&#8221;
ya era la mejor aproximación binaria posible de 1/10, de manera que intentar
redondearla nuevamente no puede mejorarla: ya era la mejor posible.</p>
<p>Otra consecuencia es que como 0.1 no es exactamente 1/10, sumar diez valores
de 0.1 quizás tampoco dé exactamente 1.0:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">suma</span> <span class="o">=</span> <span class="mf">0.0</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="gp">... </span>    <span class="n">suma</span> <span class="o">+=</span> <span class="mf">0.1</span>
<span class="gp">...</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">suma</span>
<span class="go">0.99999999999999989</span>
</pre></div>
</div>
<p>La aritmética de punto flotante binaria tiene varias sorpresas como esta.
El problema con &#8220;0.1&#8221; es explicado con detalle abajo, en la sección &#8220;Error
de Representación&#8221;.  Mirá los Peligros del Punto Flotante (en inglés,
<a class="reference external" href="http://www.lahey.com/float.htm">The Perils of Floating Point</a>) para una
más completa recopilación de otras sorpresas normales.</p>
<p>Como dice cerca del final, &#8220;no hay respuestas fáciles&#8221;.  A pesar de eso,
¡no le tengas mucho miedo al punto flotante!  Los errores en las operaciones
flotantes de Python se heredan del hardware de punto flotante, y en la
mayoría de las máquinas están en el orden de no más de una 1 parte en
2**53 por operación.  Eso es más que adecuado para la mayoría de las
tareas, pero necesitás tener en cuenta que no es aritmética decimal, y que
cada operación de punto flotante sufre un nuevo error de redondeo.</p>
<p>A pesar de que existen casos patológicos, para la mayoría de usos casuales
de la aritmética de punto flotante al final verás el resultado que esperás
si simplemente redondeás lo que mostrás de tus resultados finales al número
de dígitos decimales que esperás.  <tt class="xref docutils literal"><span class="pre">str()</span></tt> es normalmente suficiente, y
para un control más fino mirá los parámetros del método de formateo
<tt class="xref docutils literal"><span class="pre">str.format()</span></tt> en <em class="xref">formatstrings</em>.</p>
<div class="section" id="error-de-representacion">
<span id="tut-fp-error"></span><h2>15.1. Error de Representación<a class="headerlink" href="#error-de-representacion" title="Enlazar permanentemente con este título">¶</a></h2>
<p>Esta sección explica el ejemplo &#8220;0.1&#8221; en detalle, y muestra como en la
mayoría de los casos vos mismo podés realizar un análisis exacto como este.
Se asume un conocimiento básico de la representación de punto flotante
binario.</p>
<p><em>Error de representación</em> se refiere al hecho de que algunas (la
mayoría) de las fracciones decimales no pueden representarse exactamente
como fracciones binarias (en base 2).  Esta es la razón principal de por qué
Python (o Perl, C, C++, Java, Fortran, y tantos otros) frecuentemente no
mostrarán el número decimal exacto que esperás:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="mf">0.1</span>
<span class="go">0.10000000000000001</span>
</pre></div>
</div>
<p>¿Por qué es eso?  1/10 no es representable exactamente como una fracción
binaria.  Casi todas las máquinas de hoy en día (Noviembre del 2000) usan
aritmética de punto flotante IEEE-754, y casi todas las plataformas mapean
los flotantes de Python al &#8220;doble precisión&#8221; de IEEE-754.  Estos &#8220;dobles&#8221;
tienen 53 bits de precisión, por lo tanto en la entrada la computadora
intenta convertir 0.1 a la fracción más cercana que puede de la forma
<em>J</em>/2***N* donde <em>J</em> es un entero que contiene exactamente 53 bits.
Reescribiendo</p>
<div class="highlight-python"><pre>1 / 10 ~= J / (2**N)</pre>
</div>
<p>...como</p>
<div class="highlight-python"><pre>J ~= 2**N / 10</pre>
</div>
<p>...y recordando que <em>J</em> tiene exactamente 53 bits (es <tt class="docutils literal"><span class="pre">&gt;=</span> <span class="pre">2**52</span></tt> pero
<tt class="docutils literal"><span class="pre">&lt;</span> <span class="pre">2**53</span></tt>), el mejor valor para <em>N</em> es 56:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="mi">2</span><span class="o">**</span><span class="mi">52</span>
<span class="go">4503599627370496L</span>
<span class="gp">&gt;&gt;&gt; </span><span class="mi">2</span><span class="o">**</span><span class="mi">53</span>
<span class="go">9007199254740992L</span>
<span class="gp">&gt;&gt;&gt; </span><span class="mi">2</span><span class="o">**</span><span class="mi">56</span><span class="o">/</span><span class="mi">10</span>
<span class="go">7205759403792793L</span>
</pre></div>
</div>
<p>O sea, 56 es el único valor para <em>N</em> que deja <em>J</em> con exactamente 53 bits.
El mejor valor posible para <em>J</em> es entonces el cociente redondeado:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">q</span><span class="p">,</span> <span class="n">r</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="mi">56</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">r</span>
<span class="go">6L</span>
</pre></div>
</div>
<p>Ya que el resto es más que la mitad de 10, la mejor aproximación se obtiene
redondeándolo:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">q</span><span class="o">+</span><span class="mi">1</span>
<span class="go">7205759403792794L</span>
</pre></div>
</div>
<p>Por lo tanto la mejor aproximación a 1/10 en doble precisión 754 es eso
sobre 2**56, o</p>
<div class="highlight-python"><div class="highlight"><pre><span class="mi">7205759403792794</span> <span class="o">/</span> <span class="mi">72057594037927936</span>
</pre></div>
</div>
<p>Notá que como lo redondeamos, esto es un poquito más grande que 1/10; si no
lo hubiéramos redondeado, el cociente hubiese sido un poquito menor que
1/10.  ¡Pero no hay caso en que sea <em>exactamente</em> 1/10!</p>
<p>Entonces la computadora nunca &#8220;ve&#8221; 1/10:  lo que ve es la fracción exacta de
arriba, la mejor aproximación al flotante doble de 754 que puede obtener:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="o">.</span><span class="mi">1</span> <span class="o">*</span> <span class="mi">2</span><span class="o">**</span><span class="mi">56</span>
<span class="go">7205759403792794.0</span>
</pre></div>
</div>
<p>Si multiplicamos esa fracción por 10**30, podemos ver el valor (truncado)
de sus 30 dígitos más significativos:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="mi">7205759403792794</span> <span class="o">*</span> <span class="mi">10</span><span class="o">**</span><span class="mi">30</span> <span class="o">/</span> <span class="mi">2</span><span class="o">**</span><span class="mi">56</span>
<span class="go">100000000000000005551115123125L</span>
</pre></div>
</div>
<p>...lo que significa que el valor exacto almacenado en la computadora es
aproximadamente igual al valor decimal 0.100000000000000005551115123125.
Redondeando eso a 17 dígitos significativos da el 0.10000000000000001 que
Python muestra (bueno, mostraría en cualquier plataforma que cumpla con 754
cuya biblioteca en C haga la mejor conversión posible en entrada y
salida... ¡la tuya quizás no!).</p>
</div>
</div>


          </div>
        </div>
      </div>
      <div class="sphinxsidebar">
        <div class="sphinxsidebarwrapper">
            <h3><a href="contenido.html">Contenidos</a></h3>
            <ul>
<li><a class="reference external" href="#">15. Aritmética de Punto Flotante: Problemas y Limitaciones</a><ul>
<li><a class="reference external" href="#error-de-representacion">15.1. Error de Representación</a></li>
</ul>
</li>
</ul>

            <h4>Tema anterior</h4>
            <p class="topless"><a href="interactive.html"
                                  title="Capítulo anterior">14. Edición de entrada interactiva y sustitución de historial</a></p>
            <h3>Esta página</h3>
            <ul class="this-page-menu">
              <li><a href="_sources/floatingpoint.txt"
                     rel="nofollow">Enseñar el código</a></li>
            </ul>
          <div id="searchbox" style="display: none">
            <h3>Búsqueda rápida</h3>
              <form class="search" action="search.html" method="get">
                <input type="text" name="q" size="18" />
                <input type="submit" value="Ir a" />
                <input type="hidden" name="check_keywords" value="yes" />
                <input type="hidden" name="area" value="default" />
              </form>
              <p class="searchtip" style="font-size: 90%">
              Ingrese una palabra para buscar o un nombre de módulo, clase o función
              </p>
          </div>
          <h4><a href="/"> Volver a CdPedia </a></h4>
          <script type="text/javascript">$('#searchbox').show(0);</script>
        </div>
      </div>
      <div class="clearer"></div>
    </div>
    <div class="related">
      <h3>Navegación</h3>
      <ul>
        <li class="right" style="margin-right: 10px">
          <a href="genindex.html" title="Índice General"
             >índice</a></li>
        <li class="right" >
          <a href="interactive.html" title="14. Edición de entrada interactiva y sustitución de historial"
             >anterior</a> |</li>
        <li><a href="contenido.html">Tutorial de Python v2.6.2 documentation</a> &raquo;</li> 
      </ul>
    </div>
    <div class="footer">
      &copy; Copyright 2009, Python Software Foundation.
      Creado con <a href="http://sphinx.pocoo.org/">Sphinx</a> 0.6.5.
    </div>
  </body>
</html>
